#include <strmif.h>
#include <uuids.h>
#include "mkvsourcefilter.hpp"
#include "cenumpins.hpp"
#include "mkvsourceoutpin.hpp"
#include "mkvparser.hpp"
#include "mkvparserstreamvideo.hpp"
#include "mkvparserstreamaudio.hpp"
#include <new>
#include <cassert>
#include <vfwmsgs.h>
#include <process.h>
#ifdef _DEBUG
#include "iidstr.hpp"
#include "odbgstream.hpp"
using std::endl;
#endif

using std::wstring;
//using std::wistringstream;

namespace MkvSource
{

// {A6E80BD1-6E49-46aa-89DC-F343FC8DDDF7}
extern const CLSID CLSID_MkvSource = 
{ 0xa6e80bd1, 0x6e49, 0x46aa, { 0x89, 0xdc, 0xf3, 0x43, 0xfc, 0x8d, 0xdd, 0xf7 } };


Filter::Lock::Lock() : m_hMutex(0)
{
}


Filter::Lock::~Lock()
{
    Release();
}


HRESULT Filter::Lock::Seize(Filter* pFilter, DWORD timeout)
{
    assert(m_hMutex == 0);
    assert(pFilter);
    
    DWORD index;

    const HRESULT hr = CoWaitForMultipleHandles(
                            0,  //wait flags
                            timeout, 
                            1, 
                            &pFilter->m_hMutex, 
                            &index);

    //despite the "S" in this name, this is an error                        
    if (hr == RPC_S_CALLPENDING)
        return VFW_E_TIMEOUT;

    if (FAILED(hr))
        return hr;
                
    assert(index == 0);

    m_hMutex = pFilter->m_hMutex;
        
    return S_OK;
}


void Filter::Lock::Release()
{
    if (m_hMutex)
    {
        const BOOL b = ReleaseMutex(m_hMutex);
        assert(b);
        b;
        
        m_hMutex = 0;
    }
}



HRESULT CreateInstance(
    IClassFactory* pClassFactory,
    IUnknown* pOuter, 
    const IID& iid, 
    void** ppv)
{
    if (ppv == 0)
        return E_POINTER;
        
    *ppv = 0;

    if ((pOuter != 0) && (iid != __uuidof(IUnknown)))
        return E_INVALIDARG;
    
    Filter* p = new (std::nothrow) Filter(pClassFactory, pOuter);
    
    if (p == 0)
        return E_OUTOFMEMORY;
        
    assert(p->m_nondelegating.m_cRef == 0);
    
    const HRESULT hr = p->m_nondelegating.QueryInterface(iid, ppv);
    
    if (SUCCEEDED(hr))
    {
        assert(*ppv);
        assert(p->m_nondelegating.m_cRef == 1);
        
        return S_OK;
    }
    
    assert(*ppv == 0);
    assert(p->m_nondelegating.m_cRef == 0);

    delete p;
    p = 0;

    return hr;
}


#pragma warning(disable:4355)  //'this' ptr in member init list
Filter::Filter(IClassFactory* pClassFactory, IUnknown* pOuter)
    : m_pClassFactory(pClassFactory),
      m_nondelegating(this),
      m_pOuter(pOuter ? pOuter : &m_nondelegating),
      m_state(State_Stopped),
      m_clock(0),
      m_hThread(0),
      m_hStop(0),
      m_pSegment(0)
{
    m_pClassFactory->LockServer(TRUE);
            
    m_hMutex = CreateMutex(0, 0, 0);
    assert(m_hMutex);  //TODO
    
    m_hStop = CreateEvent(0, 0, 0, 0);
    assert(m_hStop);  //TODO
    
    m_info.pGraph = 0;
    m_info.achName[0] = L'\0';
    
#ifdef _DEBUG        
    odbgstream os;
    os << "mkvsrc::ctor" << endl;
#endif    
}
#pragma warning(default:4355)


Filter::~Filter()
{
#ifdef _DEBUG
    odbgstream os;
    os << "mkvsrc::dtor" << endl;
#endif

    while (!m_pins.empty())
    {
        Outpin* p = m_pins.back();
        assert(p);
        
        m_pins.pop_back();
        delete p;
    }
    
    delete m_pSegment;

    assert(m_hThread == 0);

    BOOL b = CloseHandle(m_hMutex);
    assert(b);
    
    b = CloseHandle(m_hStop);
    assert(b);

    m_pClassFactory->LockServer(FALSE);
}      


void Filter::Init()
{
    assert(m_hThread == 0);
    
    const BOOL b = ResetEvent(m_hStop);
    assert(b);
    
    const uintptr_t h = _beginthreadex(
                            0,  //security
                            0,  //stack size
                            &Filter::ThreadProc,
                            this,
                            0,   //run immediately
                            0);  //thread id
                            
    m_hThread = reinterpret_cast<HANDLE>(h);
    assert(m_hThread);
}


void Filter::Final()
{
    if (m_hThread == 0)
        return;
        
    BOOL b = SetEvent(m_hStop);
    assert(b);
    
    const DWORD dw = WaitForSingleObject(m_hThread, INFINITE);
    assert(dw == WAIT_OBJECT_0);
 
    b = CloseHandle(m_hThread);
    assert(b);
    
    m_hThread = 0;   
}


Filter::nondelegating_t::nondelegating_t(Filter* p)
    : m_pFilter(p),
      m_cRef(0)  //see CreateInstance
{
}


Filter::nondelegating_t::~nondelegating_t()
{
}


HRESULT Filter::nondelegating_t::QueryInterface(
    const IID& iid, 
    void** ppv)
{
    if (ppv == 0)
        return E_POINTER;
        
    IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv);
     
    if (iid == __uuidof(IUnknown))    
    {
        pUnk = this;  //must be nondelegating
    }   
    else if ((iid == __uuidof(IBaseFilter)) ||
             (iid == __uuidof(IMediaFilter)) ||
             (iid == __uuidof(IPersist)))
    {
        pUnk = static_cast<IBaseFilter*>(m_pFilter);
    }
    else if (iid == __uuidof(IFileSourceFilter))
    {
        pUnk = static_cast<IFileSourceFilter*>(m_pFilter);
    }
    else if (iid == __uuidof(IAMFilterMiscFlags))
    {
        pUnk = static_cast<IAMFilterMiscFlags*>(m_pFilter);
    }
    else
    {
#if 0
        wodbgstream os;
        os << "mkvsource::filter::QI: iid=" << IIDStr(iid) << std::endl;
#endif        
        pUnk = 0;
        return E_NOINTERFACE;
    }

    pUnk->AddRef();
    return S_OK;
}


ULONG Filter::nondelegating_t::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

    
ULONG Filter::nondelegating_t::Release()
{
    if (LONG n = InterlockedDecrement(&m_cRef))
        return n;
    
    delete m_pFilter;
    return 0;
}


HRESULT Filter::QueryInterface(const IID& iid, void** ppv)
{
    return m_pOuter->QueryInterface(iid, ppv);
}


ULONG Filter::AddRef()
{
    return m_pOuter->AddRef();
}


ULONG Filter::Release()
{
    return m_pOuter->Release();
}


HRESULT Filter::GetClassID(CLSID* p)
{
    if (p == 0)
        return E_POINTER;
        
    *p = CLSID_MkvSource;
    return S_OK;
}



HRESULT Filter::Stop()
{
    //Stop is a synchronous operation: when it completes,
    //the filter is stopped.

    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;
        
    switch (m_state)
    {
        case State_Paused:
        case State_Running:
            
            //Stop is synchronous.  When stop completes, all threads
            //should be stopped.  What does "stopped" mean"  In our
            //case it probably means "terminated".
            //It's a bit tricky here because we hold the filter
            //lock.  If threads need to acquire filter lock
            //then we'll have to release it.  Only the FGM can call
            //Stop, etc, so there's no problem to release lock
            //while Stop is executing, to allow threads to acquire
            //filter lock temporarily.
            //The streaming thread will receiving an indication
            //automatically (assuming it's connected), either via
            //GetBuffer or Receive, so there's nothing this filter
            //needs to do to tell the streaming thread to stop.
            //One implementation strategy is to have build a
            //vector of thread handles, and then wait for a signal
            //on one of them.  When the handle is signalled 
            //(meaning that the thread has terminated), then 
            //we remove that handle from the vector, close the
            //handle, and the wait again.  Repeat until the
            //all threads have been terminated.
            //We also need to clean up any unused samples,
            //and decommit the allocator.  (In fact, we could
            //decommit the allocator immediately, and then wait
            //for the threads to terminated.)
            
            lock.Release();

            OnStop();            
            
            hr = lock.Seize(this);
            assert(SUCCEEDED(hr));  //TODO
            
            break;            

        case State_Stopped:
        default:
            break;
    }
    
    m_state = State_Stopped;
    return S_OK;
}


HRESULT Filter::Pause()
{
    //Unlike Stop(), Pause() can be asynchronous (that's why you have
    //GetState()).  We could use that here to build the samples index.    

    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;
        
    switch (m_state)
    {
        case State_Stopped:
            OnStart();
            break;

        case State_Running:
        case State_Paused:
        default:
            break;            
    }
    
    m_state = State_Paused;
    return S_OK;
}


HRESULT Filter::Run(REFERENCE_TIME start)
{
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;
    
    switch (m_state)
    {
        case State_Stopped:
            OnStart();
            break;

        case State_Paused:        
        case State_Running:
        default:
            break;            
    }

    m_start = start;
    m_state = State_Running;
    
    return S_OK;
}


HRESULT Filter::GetState( 
    DWORD /* timeout */ ,
    FILTER_STATE* p)
{
    if (p == 0)
        return E_POINTER;
        
    //What the GetState.timeout parameter refers to is not to locking
    //the filter, but rather to waiting to determine the current state.
    //A request to Stop is always synchronous (hence no timeout parameter), 
    //but a request to Pause can be asynchronous, so the caller can say
    //how long he's willing to wait for the transition (to paused) to
    //complete.
    
    //TODO: implement a waiting scheme here.  We'll probably have to 
    //use SignalObjectAndWait atomically release the mutex and then
    //wait for the condition variable to change.    
    //if (hr == VFW_E_TIMEOUT)
    //    return VFW_S_STATE_INTERMEDIATE;
    
    Lock lock;
    
    const HRESULT hr = lock.Seize(this);
    
    //The lock is only used for synchronization.  If Seize fails,
    //it means there's a serious problem with the filter.
    
    if (FAILED(hr))
        return E_FAIL;

    *p = m_state;
    return S_OK;
}



HRESULT Filter::SetSyncSource( 
    IReferenceClock* clock)
{
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;

    if (m_clock)
        m_clock->Release();
        
    m_clock = clock;
    
    if (m_clock)
        m_clock->AddRef();

    return S_OK;
}


HRESULT Filter::GetSyncSource( 
    IReferenceClock** pclock)
{
    if (pclock == 0)
        return E_POINTER;
        
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;

    IReferenceClock*& clock = *pclock;
        
    clock = m_clock;
    
    if (clock)
        clock->AddRef();

    return S_OK;
}


HRESULT Filter::EnumPins(IEnumPins** pp)
{
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;
        
    if (m_pins.empty())
        return CEnumPins::CreateInstance(0, 0, pp);        
        
    Outpin* const* const i = &m_pins[0];
    const ULONG n = static_cast<ULONG>(m_pins.size());
        
    return CEnumPins::CreateInstance<Outpin>(i, n, pp);
}



HRESULT Filter::FindPin( 
    LPCWSTR id1,
    IPin** pp)
{
    if (pp == 0)
        return E_POINTER;
        
    IPin*& p = *pp;
    p = 0;
    
    if (id1 == 0)
        return E_INVALIDARG;
        
    typedef pins_t::const_iterator iter_t;

    iter_t i = m_pins.begin();
    const iter_t j = m_pins.end();
    
    while (i != j)
    {
        Pin* const pPin = *i++;

        const wstring& id2_ = pPin->m_id;
        const wchar_t* const id2 = id2_.c_str();
        
        if (wcscmp(id1, id2) == 0)  //case-sensitive
        {
            p = pPin;
            p->AddRef();
            
            return S_OK;
        }
    }    
    
    return VFW_E_NOT_FOUND;
}



HRESULT Filter::QueryFilterInfo(FILTER_INFO* p)
{
    if (p == 0)
        return E_POINTER;
        
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;

    enum { size = sizeof(p->achName)/sizeof(WCHAR) };
    const errno_t e = wcscpy_s(p->achName, size, m_info.achName);
    e;
    assert(e == 0);

    p->pGraph = m_info.pGraph;
    
    if (p->pGraph)
        p->pGraph->AddRef();
        
    return S_OK;
}


HRESULT Filter::JoinFilterGraph( 
    IFilterGraph *pGraph,
    LPCWSTR name)
{
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;
    
    //NOTE: 
    //No, do not adjust reference counts here!
    //Read the docs for the reasons why.
    //ENDNOTE.    
    
    m_info.pGraph = pGraph;

    if (name == 0)
        m_info.achName[0] = L'\0';
    else
    {
        enum { size = sizeof(m_info.achName)/sizeof(WCHAR) };
        const errno_t e = wcscpy_s(m_info.achName, size, name);
        e;
        assert(e == 0);  //TODO
    }
    
    return S_OK;
}


HRESULT Filter::QueryVendorInfo(LPWSTR* pstr)
{
    if (pstr == 0)
        return E_POINTER;
        
    wchar_t*& str = *pstr;
    
    str = 0;
    return E_NOTIMPL;
}


HRESULT Filter::Load(LPCOLESTR filename, const AM_MEDIA_TYPE* pmt)
{
    pmt;
    
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;
        
    if (m_file.IsOpen())
        return E_UNEXPECTED;
        
    assert(m_pSegment == 0);
        
    hr = m_file.Open(filename);
    
    if (FAILED(hr))
        return hr;
        
    hr = CreateSegment();
    
    if (FAILED(hr))
    {
        m_file.Close();
        return hr;
    }
        
    m_filename = filename;
        
    return S_OK;
}


HRESULT Filter::CreateSegment()
{
    assert(m_file.IsOpen());
    assert(m_pSegment == 0);
    
    __int64 result, pos;
    
    //TODO: must initialize header to defaults
    
    MkvParser::EBMLHeader h;
    
    result = h.Parse(&m_file, pos);
    
    if (result < 0)  //error
        return static_cast<HRESULT>(result);
        
    assert(result == 0);  //all data available in local file
        
    if (h.m_version > 1)
        return VFW_E_INVALID_FILE_FORMAT;
        
    if (h.m_maxIdLength > 8)
        return VFW_E_INVALID_FILE_FORMAT;
    
    if (h.m_maxSizeLength > 8)
        return VFW_E_INVALID_FILE_FORMAT;
        
    if (_stricmp(h.m_docType.c_str(), "matroska") != 0)
        return VFW_E_INVALID_FILE_FORMAT;
        
    //Just the EBML header has been consumed.  pos points
    //to start of (first) segment.
    
    MkvParser::Segment* p;
    
    result = MkvParser::Segment::CreateInstance(&m_file, pos, p);
    
    if (result < 0)
        return static_cast<HRESULT>(result);
        
    assert(result == 0);  //all data available in local file        
    assert(p);
    
    std::auto_ptr<MkvParser::Segment> pSegment(p);
    
#if 0  //TODO: this is for incremental loading
    while (pSegment->GetFirst() == 0)  //cluster
    {
        if (pSegment->Unparsed() <= 0)
            return S_FALSE;  //no clusters
    
        result = pSegment->Parse();
        
        if (result < 0)
            return static_cast<HRESULT>(result);
            
        assert(result == 0);            
    }
#else
    const HRESULT hr = pSegment->Load();
    
    if (FAILED(hr))
        return hr;
#endif
    
    const MkvParser::Tracks* const pTracks = pSegment->GetTracks();
    
    if (pTracks == 0)
        return S_FALSE;
    
    assert(m_pins.empty());

    using namespace MkvParser;
    
    typedef TCreatePins<VideoTrack, VideoStream> EV;    
    pTracks->EnumerateVideoTracks(EV(this, &VideoStream::CreateInstance));
    
    typedef TCreatePins<AudioTrack, AudioStream> EA;
    pTracks->EnumerateAudioTracks(EA(this, &AudioStream::CreateInstance));
    
    if (m_pins.empty())
        return VFW_E_INVALID_FILE_FORMAT;  //TODO: better return value here?
        
    m_pSegment = pSegment.release();

    return S_OK;
}


HRESULT Filter::GetCurFile(LPOLESTR* pname, AM_MEDIA_TYPE* pmt)
{
    if (pmt)
        memset(pmt, 0, sizeof(AM_MEDIA_TYPE));  //TODO
        
    if (pname == 0)
        return E_POINTER;
        
    wchar_t*& name = *pname;
    name = 0;
    
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;
    
    if (!m_file.IsOpen())
        return S_FALSE;
        
    const size_t len = m_filename.length();
    const size_t size = len + 1;
    const size_t cb = size * sizeof(wchar_t);
    
    name = (wchar_t*)CoTaskMemAlloc(cb);
    
    if (name == 0)
        return E_OUTOFMEMORY;
        
    const errno_t e = wcscpy_s(name, size, m_filename.c_str());
    e;
    assert(e == 0);
    
    return S_OK;
}


ULONG Filter::GetMiscFlags()
{
    return AM_FILTER_MISC_FLAGS_IS_SOURCE;
}


void Filter::OnStart()
{
    typedef pins_t::iterator iter_t;
    
    iter_t i = m_pins.begin();
    const iter_t j = m_pins.end();
    
    while (i != j)
    {
        Outpin* const pPin = *i++;
        assert(pPin);
        
        pPin->Init();
    }
    
    Init();
}


void Filter::OnStop()
{
    Final();
    
    typedef pins_t::iterator iter_t;
    
    iter_t i = m_pins.begin();
    const iter_t j = m_pins.end();
    
    while (i != j)
    {
        Outpin* const pPin = *i++;
        assert(pPin);
        
        pPin->Final();
    }
}


int Filter::GetConnectionCount() const
{
    //filter already locked by caller
    
    int n = 0;
    
    typedef pins_t::const_iterator iter_t;
    
    iter_t i = m_pins.begin();
    const iter_t j = m_pins.end();
    
    while (i != j)
    {
        const Outpin* const pin = *i++;
        assert(pin);
        
        if (pin->m_connection)
            ++n;
    }
    
    return n;
}        


unsigned Filter::ThreadProc(void* pv)
{
    Filter* const pFilter = static_cast<Filter*>(pv);
    assert(pFilter);
    
    return pFilter->Main();
}


unsigned Filter::Main()
{
    std::vector<HANDLE> v;
    
    v.reserve(1 + m_pins.size());    
    v.push_back(m_hStop);
    
    typedef pins_t::iterator iter_t;

    iter_t i = m_pins.begin();
    const iter_t j = m_pins.end();
    
    while (i != j)
    {
        Outpin* const pPin = *i++;
        assert(pPin);

        const HANDLE h = pPin->m_hSampleCanBePopulated;
        assert(h);
                
        v.push_back(h);
    }
    
    const HANDLE* const hh = &v[0];
    const DWORD n = static_cast<DWORD>(v.size());
    
    const DWORD dwTimeout = INFINITE;
    
    for (;;)
    {
        const DWORD dw = WaitForMultipleObjects(n, hh, 0, dwTimeout);
        
#if 0
        if (dw == WAIT_TIMEOUT)
        {
            Lock lock;
            
            const HRESULT hr = lock.Seize(this);
            assert(SUCCEEDED(hr));
            
            const __int64 result = m_pSegment->Parse();
            assert(result == 0);
            
            if (m_pSegment->Unparsed() <= 0)
                dwTimeout = INFINITE;
            
            continue;
        }
#endif
        
        assert(dw >= WAIT_OBJECT_0);
        assert(dw < (WAIT_OBJECT_0 + n));
        
        if (dw == WAIT_OBJECT_0)  //hStop
            return 0;
            
        const DWORD idx = dw - (WAIT_OBJECT_0 + 1);
        assert(idx < m_pins.size());
        
        PopulateSamples(hh + 1, idx);
    }
}


void Filter::PopulateSamples(const HANDLE* hh_begin, DWORD idx)
{
    //idx represents the pin that just signalled
    
    for (;;)
    {
        Outpin* const pPin = m_pins[idx];
        assert(pPin);
        
        pPin->PopulateSample();
        
        if (++idx >= m_pins.size())
            return;

        const HANDLE* const hh = hh_begin + idx;
        const DWORD n = static_cast<DWORD>(m_pins.size()) - idx;
        
        const DWORD dw = WaitForMultipleObjects(n, hh, 0, 0);
        
        if (dw == WAIT_TIMEOUT)
            return;
                            
        assert(dw >= WAIT_OBJECT_0);
        assert(dw < (WAIT_OBJECT_0 + n));
        
        idx += dw - WAIT_OBJECT_0;
    }
}


} //end namespace MkvSource

